<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>打字机效果</title>
  <style>
    .cursor {
      position: absolute;
      display: inline-block;
      width: 1.5px;
      height: 16px;
      background-color: #000;
      animation: blink 1s infinite;
      transform: translate(2px, 3px);
    }

    .cursor2 {
      margin: 4px;
      position: absolute;
      display: inline-block;
      width: 1.5px;
      height: 16px;
      background-color: #000;
      animation: blink 0.9s steps(1) infinite;
    }

    @keyframes blink {

      0%,
      100% {
        opacity: 1;
      }

      50% {
        opacity: 0;
      }
    }

    @keyframes blink2 {
      0% {
        visibility: visible;
      }

      50% {
        visibility: hidden;
      }

      100% {
        visibility: visible;
      }
    }

    .box1 {
      line-height: 22px;
      width: 300px;
      font-size: 16px;
      padding: 10px;
      border: 1px solid pink;
      margin-bottom: 10px;
      min-height: 100px;
    }

    .content {
      padding: 16px;
      font-weight: 600;
      line-height: 1.4;
    }
  </style>
</head>

<body>
  <div class="box1">
    <span class="cursor"></span>
  </div>
  <button class="btn">添加文字</button>
  <button class="btn2">停止</button>

  <hr>

  <pre class="content">
  </pre>
  <hr>

  <!-- <span class="cursor2"></span> -->

  <hr>

  <script>
    const randomTextArr = ["萨嘎", '三', "agas", '大厦', '阿萨法施工', 'saf', '啊', '收到', '三个哈哈哈', '阿事实上事实上事实上', '事实上事实上少时诵诗书', '叫哦大家搜狗号度搜化工三打哈干撒的很尬山东干红手打很尬搜哈', '时间几节课MVvvvvvvvvvv啪啪啪啪啪啪PPT科技我IQ和瓦暖气，你', '撒啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊就你跟贵公司懂法守法收入与武器我先把发发花洒就你跟贵公司懂法守法收入与武器我先把发发花洒'];
    const box1 = document.querySelector('.box1');
    const btn = document.querySelector('.btn');
    let showText = '';
    let addTextArr = [];
    let timer = null;

    btn.onclick = () => {
      getRandomText();
      updateText();
    }

    function getRandomText() {
      const randomTextArrLength = randomTextArr.length;
      let randomNum = Math.random();
      let addText = randomTextArr[Math.floor(Math.random() * randomTextArrLength)];
      addTextArr.push(addText);
      console.log(addText)
    }

    function updateText() {
      let index = 0;
      if (!timer) {
        timer = setInterval(() => {
          if (addTextArr.length > 0) {
            if (index < addTextArr[0].length) {
              box1.innerHTML = showText + addTextArr[0][index] + `<span class="cursor"></span>`;
              showText += addTextArr[0][index];
              index++;
            } else {
              index = 0;
              box1.innerHTML = showText;
              addTextArr.shift();
            }
          } else {
            clearInterval(timer);
            timer = null;
          }
        }, 50)
      }
    }
  </script>


  <script>





    //     const testStr = `
    // Fetch API 支持流式响应，这意味着你可以以流的形式读取来自服务器的数据，而不是等待整个响应被接收。这对于处理大型文件或实时数据流非常有用。
    // 要使用 Fetch API 进行流式处理，你可以设置 response 对象的 body 属性，它是一个 ReadableStream 对象。
    // 然后，你可以对这个流使用 ReadableStream 的方法，比如 getReader()、read() 和 cancel()。

    // 以下是使用 Fetch API 进行流式处理的基本步骤：

    // 1. 发起请求并获取流
    // fetch(url, {
    //   method: 'GET',
    //   headers: {
    //     // 可以设置必要的请求头
    //   },
    //   // 其他配置...
    // })
    // .then(response => {
    //   if (!response.body) {
    //     throw new Error('响应中没有流');
    //   }
    //   // 获取ReadableStream
    //   const reader = response.body.getReader();
    //   // ...
    // })
    // .catch(error => {
    //   console.error('请求失败:', error);
    // });
    // 2. 读取流中的数据
    // // 继续上面的代码
    // const readStream = async (reader) => {
    //   while (true) {
    //     const { done, value } = await reader.read(); // 读取流中的数据
    //     if (done) {
    //       console.log('流已结束');
    //       break;
    //     }
    //     // 处理接收到的数据块
    //     console.log(value);
    //     // 如果是文本数据，可以使用TextDecoder解码
    //     const decoder = new TextDecoder();
    //     const decodedData = decoder.decode(value);
    //     console.log(decodedData);
    //   }
    // };

    // // 开始读取流
    // readStream(reader);
    // 3. 错误处理和取消流
    // // 继续上面的代码
    // reader.closed.catch(error => {
    //   console.error('读取器关闭失败:', error);
    // });

    // // 如果需要取消流
    // reader.cancel().then(() => {
    //   console.log('流已取消');
    // });
    // 示例：流式读取JSON数据
    // 如果你正在从一个以JSON格式流式传输数据的API获取数据，你需要手动处理数据帧的边界。以下是一个示例：

    // const streamToJson = async (stream) => {
    //   const reader = stream.getReader();
    //   let chunks = '';

    //   while (true) {
    //     const { done, value } = await reader.read();
    //     if (done) break;
    //     chunks += new TextDecoder().decode(value);
    //     // 假设每个JSON对象都以\\n结尾
    //     const jsonChunks = chuns.split('\\n');
    //     for (let i = 0; i < jsonChunks.length - 1; i++) {
    //       const json = JSON.parse(jsonChunks[i]);
    //       // 处理每个JSON对象
    //       console.log(json);
    //     }
    //     chunks = jsonChunks[jsonChunks.length - 1];
    //   }

    //   reader.releaseLock();
    // };

    // fetch(url)
    // .then(response => {
    //   if (response.ok) {
    //     return response.body;
    //   }
    //   throw new Error('网络响应不正确');
    // })
    // .then(stream => streamToJson(stream))
    // .catch(error => console.error('请求或流处理失败:', error));
    // 流式处理允许你在数据到达时立即处理它，而不是等待整个响应被接收和解析。这对于提高性能和减少内存使用非常有用。
    // `
    const testStr = `Fetch API 支持流式响应，这意味着你可以以流的形式读取来自服务器的数据，而不是等待整个响应被接收。
流式处理允许你在数据到达时立即处理它，而不是等待整个响应被接收和解析。这对于提高性能和减少内存使用非常有用。
`




    /**
     * 打字机效果
     */
    class MessageManager {
      messageList = [];
      timer = null;
      timerDelay = 50;
      stopFlag = false; // 停止标志，如果设置了停止，但是队列没走完，就会等队列走完之后再停止

      constructor(
        onMessage,
        onFinish
      ) {
        this.onFinish = onFinish;
        this.onMessage = onMessage;
      }

      start() {
        this.consume();
        this.timer = setInterval(() => {
          if (this.messageList.length > 0) {
            this.consume();
          } else {
            if (this.stopFlag) {
              this.immediatelyStop();
            }
          }
        }, this.timerDelay);
      }

      consume() {
        if (this.messageList.length > 0) {
          const str = this.messageList.shift();
          str && this.onMessage(str);
        }
      }

      add(str) {
        if (!str) return;
        const strChars = str.split('');
        this.messageList.push(...strChars);
      }

      stop() {
        this.stopFlag = true;
      }

      immediatelyStop() {
        // 立刻停止
        clearInterval(this.timer);
        this.timer = null;
        this.stopFlag = false;
        this.messageList = [];
        this.onFinish();
      }
    }









    class Typewriter {
      queue = []
      consuming = false;
      timmer
      constructor(onConsume) {
        this.queue = []
        this.onConsume = onConsume
      }
      #dynamicSpeed() {
        const speed = 1000 / this.queue.length;
        if (speed > 200) {
          return 200;
        } else {
          return speed
        }
      }
      add(str) {
        if (!str) return;
        this.queue.push(...str.split(''));
      }
      consume = () => {
        if (this.queue.length > 0) {
          const str = this.queue.shift();
          if (str) this.onConsume(str);
        }
      };
      next = () => {
        this.consume()
        // 根据队列中字符的数量来设置消耗每一帧的速度，用定时器消耗
        this.timmer = setTimeout(() => {
          this.consume()
          if (this.consuming) {
            this.next()
          }
        }, this.dynamicSpeed())

      };
      start = () => {
        this.consuming = true;
        this.next();
      };
      done = () => {
        this.consuming = false;
        clearTimeout(this.timmer);
        this.timmer = null;
        // 把queue中剩下的字符一次性消费
        this.onConsume(this.queue.join(''))
        this.queue = [];
      };
    }
    const contentDom = document.querySelector('.content')
    // const typewriter = new Typewriter((str) => {
    //   console.log('==str====>', str)
    //   contentDom.textContent += str
    //   document.documentElement.scrollTop = document.documentElement.scrollHeight;
    //   contentDom?.insertAdjacentHTML('beforeend', '<span class="cursor2"></span>')
    // })
    // typewriter.start()
    const typewriter = new MessageManager((str) => {
      console.log('==str====>', str)
      contentDom.textContent += str
      document.documentElement.scrollTop = document.documentElement.scrollHeight;
      contentDom?.insertAdjacentHTML('beforeend', '<span class="cursor2"></span>')
    }, () => {
      console.log('== done ====>',)
    })

    const strArr = testStr.split('')
    setInterval(() => {
      const showText = strArr.shift()
      if (showText) {
        typewriter.add(showText)
      } else {
        // typewriter.done()
        typewriter.stop()
      }
    }, 10)


    setTimeout(() => {
      typewriter.start()

    }, 11);
    const btn2 = document.querySelector('.btn2');
    btn2.addEventListener('click', () => {
      typewriter.immediatelyStop(0)
    })

  </script>
</body>

</html>